<?php
/**
 * This file is part of DomSocketServer.
 * 
 * DomSocketServer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * DomSocketServer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with DomSocketServer.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * @link http://inditel.ee/
 * @copyright 2007-2009 Inditel Meedia OÜ
 * @author Oliver Leisalu <oliver@inditel.ee>
 */

/**
 * Usage sample
 * <code>
$server = new DomSocketServer( $address, $port, 'myHandler', 10);
$server->run();
function myHandler( &$input, &$connectionId, &$connection, SocketServer $server ) {
	if( $input == 'myHandler' ) {
		$server->send( $connection,"Hello you to!");
	}
	return true;
}
 * </code>
 *
 * @author  Oliver Leisalu
 * @package DomSocketServer
 */
class DomSocketServer {
	
	private $address;
	
	private $port;
	
	private $maxConnections;
	
	private $handler;
	
	private $socket;
	
	private $clientClassName;
	
	private $clients = array();
	
	private $connections = array();
	
	private $log;
	
	public $liveTime = 30;
	
	private $terminate = false;
	
	public function __construct( $address, $port, $maxConnections, $clientClassName = 'DomSocketServerClient' ) {
		$this->address = $addres;
		$this->port = $port;
		$this->maxConnections = $maxConnections;
		$this->clientClassName = $clientClassName;
		
		$this->log = fopen( DOM_LOG_DIR_PATH . 'DomSocketServerLog.txt','a+');
	}
	
	public function run() {
		
		set_time_limit(0);
		ob_implicit_flush();
		
		// Create a TCP Stream socket
		$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		
		// Bind the socket to an address/port
		if( !socket_bind($this->socket, $this->address, $this->port) )
			return false;
		
		// Start listening for connections
		socket_listen( $this->socket );
		
		$this->log('Server started');
	    
	    $start = strtotime('now');

		while( $this->socket ) {
			$this->loop();
			if( $this->terminate === true or $this->liveTime > 0 && $start < strtotime('now') - $this->liveTime )
				break;
		}
		
		$this->terminate = false;
		$this->close();
	}
	
	private function findFreeConnectionId() {
		for( $i=0; $i<$this->maxConnections; $i++) {
			if( $this->connections[$i] === NULL)
				return $i;
		}
		return false;
	}
	
	private function loop() {
		
		// check if there is a client trying to connect
		if ( socket_select($read = array($this->socket), $write = NULL, $except = NULL, 0) == 1 ) {

			$freeId = $this->findFreeConnectionId();
			if( $freeId === false) {
				$this->log('Max connections '.$this->maxConnections.' reached');
			} else {
				
				$this->connections[$freeId] = socket_accept( $this->socket );
			    $className = $this->clientClassName;
				$this->clients[$freeId] = new $className($this);
				$this->clients[$freeId]->bind( $freeId, $this->connections[$freeId] );
			}
		}

		// listen to clients
		if( empty($this->connections) === true )
			return;
		
		$read = $this->connections;
		$n = socket_select( $read, $write = NULL, $except = NULL, 0);
	    
	    foreach( $read as $id => &$connection ) {
	    	$this->handleRequest( $id );
	    }
	}
	
	protected function handleRequest( $id ) {
		
		
		try {
		    $input = trim(socket_read( $this->connections[$id], 1024 ));
		} catch( Exception $e ) {
		   $this->clients[$id]->closed();
		   return;
		}
	
		if( $input == NULL )
			return;
		    	
		$this->log('<b style="color:green">INPUT['.$id.']:</b><br />'.$input);
		
		if( $input == 'exit' ) {

	   		$this->clients[$id]->close();
			return;
			
	   	} else if( $input == 'ping') {

	   		$this->clients[$id]->send('pong');
	   		
	   	} else {

	   		try {
				$this->clients[$id]->onData( $input );
	   		} catch( Exception $e ) {
	   			throw $e;
	   		}
		}
	}
	
	public function destroyClient( DomSocketServerClient $client ) {
		unset($this->clients[$client->getId()]);
		unset($this->connections[$client->getId()]);
		$this->log('Client['.$client->getId().'] destroyed');
	}
		
	public function sendToAll( $message ) {
		foreach( $this->clients as $id => $client )
			$client->send( $message );
	}
	
	public function terminate() {
		$this->terminate = true;
	}
	
	public function close() {
		$this->log('Server shutdown');
		foreach( $this->clients as $client )
			$client->close();
		socket_close( $this->socket );
		$this->socket = NULL;
		$this->log('Server closed');
	}
	
	public function log( $message ) {
		fwrite( $this->log, date('Y-m-d H:i:s').' '.$message."\r\n");
	}
	
	public function __destruct() {
		fclose($this->log);
	}
	
}
?>